package org.wabson.alfresco.translations;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class BundleGroupComparator
{
    private static final String PROPERTIES_FILE_SUFFIX = ".properties";
    
    private BundleLoader baseLoader;
    private BundleLoader compareToLoader;
    private String baseSuffix;
    private String compareToSuffix;
    
    public BundleGroupComparator(BundleLoader baseLoader, BundleLoader compareToLoader)
    {
        this.baseLoader = baseLoader;
        this.compareToLoader = compareToLoader;
    }

    public BundleGroupComparator(BundleLoader baseLoader,
            BundleLoader compareToLoader, String baseSuffix,
            String compareToSuffix)
    {
        this.baseLoader = baseLoader;
        this.compareToLoader = compareToLoader;
        this.baseSuffix = baseSuffix;
        this.compareToSuffix = compareToSuffix;
    }
    
    public BundleGroupComparisonResult compare(String[] bundles, String[] ignoreBundles, boolean compareValues)
    {
        BundleGroupComparisonResult result = new BundleGroupComparisonResult();
        
        Set<String> allFiles = new TreeSet<String>();
        BundleComparator bc = new BundleComparator();
        
        List<String> ignoreFileList = null;
        if (ignoreBundles != null)
        {
            ignoreFileList = new ArrayList<String>(ignoreBundles.length);
            for (String b : ignoreBundles)
            {
                ignoreFileList.add(b);
            }
        }
        
        if (bundles == null)
        {
            Set<String> propFiles = baseLoader.findBundleFiles();
            Set<String> cmpFiles = compareToLoader.findBundleFiles();
            
            // Filter files so that we only use those with the correct suffix
            for (String f : propFiles)
            {
                if (baseSuffix == null || baseSuffix.equals(getSuffix(f)))
                {
                    f = removeSuffix(f);
                    allFiles.add(f);
                    //System.err.println("Added base file " + f);
                }
            }
            for (String f : cmpFiles)
            {
                if (compareToSuffix == null || compareToSuffix.equals(getSuffix(f)))
                {
                    f = removeSuffix(f);
                    allFiles.add(f);
                    //System.err.println("Added comparison file " + f);
                }
            }
        }
        else
        {
            for (String bundle : bundles)
            {
               allFiles.add(bundle);
            }
        }
        
        for (String pf : allFiles)
        {
            if (ignoreFileList != null && ignoreFileList.contains(pf))
            {
                continue;
            }
            
            String baseFile = pf, compareToFile = pf;
            
            if (baseSuffix != null)
            {
                int pos = baseFile.lastIndexOf('.');
                if (pos > 0)
                {
                    baseFile = baseFile.substring(0, pos) + baseSuffix + 
                        baseFile.substring(pos);
                }
            }
            if (compareToSuffix != null)
            {
                int pos = compareToFile.lastIndexOf('.');
                if (pos > 0)
                {
                    compareToFile = compareToFile.substring(0, pos) + compareToSuffix + 
                    compareToFile.substring(pos);
                }
            }
            
            //System.err.println("Comparing " + baseFile + " with " + compareToFile);
            
            // Compare the files
            
            if (!compareToLoader.exists(compareToFile))
            {
                try
                {
                    // Add to extra list
                    if (pf.endsWith(PROPERTIES_FILE_SUFFIX))
                    {
                        result.addExtra(baseFile, baseLoader.load(baseFile));
                    }
                    else
                    {
                        result.addExtra(baseFile);
                    }
                }
                catch (BundleLoadingException e)
                {
                    e.printStackTrace();
                }
            }
            else if (!baseLoader.exists(baseFile))
            {
                try
                {
                    // Add to the missing list
                    if (pf.endsWith(PROPERTIES_FILE_SUFFIX))
                    {
                        result.addMissing(baseFile, compareToLoader.load(compareToFile));
                    }
                    else
                    {
                        result.addMissing(baseFile);
                    }
                }
                catch (BundleLoadingException e)
                {
                    e.printStackTrace();
                }
            }
            else
            {
                if (pf.endsWith(PROPERTIES_FILE_SUFFIX))
                {
                    try
                    {
                        MessageBundle compareToBundle = compareToLoader.load(compareToFile);
                        MessageBundle baseBundle = baseLoader.load(baseFile);
                        BundleComparisonResult bundleResult;
                        
                        if (compareValues == true)
                        {
                            bundleResult = bc.compareKeysAndValues(compareToBundle, baseBundle);
                        }
                        else
                        {
                            bundleResult = bc.compareKeys(compareToBundle, baseBundle);
                        }
                        
                        bundleResult.setBaseBundle(baseBundle);
                        bundleResult.setCompareToBundle(compareToBundle);
                        
                        // Add to the different list
                        result.add(baseFile, baseBundle, compareToBundle, bundleResult);
                    }
                    catch (BundleLoadingException e)
                    {
                        e.printStackTrace();
                    }
                }
                else
                {
                    result.add(baseFile);
                }
            }
        }
        
        return result;
    }

    public String getBaseSuffix()
    {
        return baseSuffix;
    }

    public void setBaseSuffix(String baseSuffix)
    {
        this.baseSuffix = baseSuffix;
    }

    public String getCompareToSuffix()
    {
        return compareToSuffix;
    }

    public void setCompareToSuffix(String compareToSuffix)
    {
        this.compareToSuffix = compareToSuffix;
    }
    
    public static boolean hasSuffix(String fileName)
    {
        String suffix = null;
        if (fileName.matches("^[-\\w\\._\\/]+_[a-z]{2}_[A-Z]{2}\\.[\\w]+$"))
        {
            suffix = fileName.substring(fileName.lastIndexOf('.') - 5, fileName.lastIndexOf('.'));
        }
        else if (fileName.matches("^[-\\w\\._\\/]+_[a-z]{2}\\.[\\w]+$"))
        {
            suffix = fileName.substring(fileName.lastIndexOf('.') - 2, fileName.lastIndexOf('.'));
        }
        return suffix != null;
    }
    
    public static String removeSuffix(String fileName)
    {
        String prefix = fileName;
        Matcher m;
        
        if ((m = Pattern.compile("^([-\\w\\._\\/]+)(_[a-z]{2}_[A-Z]{2})(\\.[\\w]+)$").matcher(fileName)).matches())
        {
            prefix = m.group(1) +  m.group(3);
        }
        else if ((m = Pattern.compile("^([-\\w\\._\\/]+)(_[a-z]{2})(\\.[\\w]+)$").matcher(fileName)).matches())
        {
            prefix = m.group(1) +  m.group(3);
        }
        return prefix;
    }
    
    public static String getSuffix(String fileName)
    {
        String suffix = "";
        Matcher m;
        
        if ((m = Pattern.compile("^([-\\w\\._\\/]+)(_[a-z]{2}_[A-Z]{2})(\\.[\\w]+)$").matcher(fileName)).matches())
        {
            suffix = m.group(2);
        }
        else if ((m = Pattern.compile("^([-\\w\\._\\/]+)(_[a-z]{2})(\\.[\\w]+)$").matcher(fileName)).matches())
        {
            suffix = m.group(2);
        }
        //System.err.println("File " + fileName + " returned suffix " + suffix);
        return suffix;
    }

}

class BundleGroupComparisonResult
{

    public static final int BUNDLE_STATE_MISSING = 1;
    public static final int BUNDLE_STATE_OK = 2;
    public static final int BUNDLE_STATE_EXTRA = 3;

    private Map<String, Integer> bundleStates;
    private Map<String, BundleComparisonResult> bundleResults;
    private Map<String, Integer> missingMessageCounts;
    private Map<String, Integer> extraMessageCounts;
    private Map<String, MessageBundle> baseBundles;
    private Map<String, MessageBundle> comparisonBundles;

    public BundleGroupComparisonResult()
    {
        reset();
    }

    /**
     * Add a bundle resource to the list
     * 
     * @param name  The name of the bundle resource
     * @param result    The result of the bundle comparison
     */
    public void add(String name, MessageBundle baseBundle, 
    		MessageBundle comparisonBundle,
    		BundleComparisonResult result)
    {
        bundleStates.put(name, BUNDLE_STATE_OK);
        bundleResults.put(name, result);
        missingMessageCounts.put(name, result.getNumMissingMessages());
        extraMessageCounts.put(name, result.getNumExtraMessages());
    }

    /**
     * Add a non-bundle resource to the list, e.g. a text file
     * 
     * @param name  The name of the resource
     */
    public void add(String name)
    {
        bundleStates.put(name, BUNDLE_STATE_OK);
        missingMessageCounts.put(name, 0);
        extraMessageCounts.put(name, 0);
    }

    /**
     * Add a bundle resource to the missing list
     * 
     * @param name  The name of the resource
     * @param bundle    The bundle object
     */
    public void addMissing(String name, MessageBundle bundle)
    {
        bundleStates.put(name, BUNDLE_STATE_MISSING);
        missingMessageCounts.put(name, bundle.getNumMessages());
        comparisonBundles.put(name, bundle);
    }

    /**
     * Add a non-bundle resource to the missing list
     * 
     * @param name  The name of the resource
     */
    public void addMissing(String name)
    {
        bundleStates.put(name, BUNDLE_STATE_MISSING);
        missingMessageCounts.put(name, 0);
    }

    /**
     * Add a bundle resource to the extra list
     * 
     * @param name  The name of the resource
     * @param bundle    The bundle object
     */
    public void addExtra(String name, MessageBundle bundle)
    {
        bundleStates.put(name, BUNDLE_STATE_EXTRA);
        extraMessageCounts.put(name, bundle.getNumMessages());
        baseBundles.put(name, bundle);
    }

    /**
     * Add a non-bundle resource to the extra list
     * 
     * @param name  The name of the resource
     */
    public void addExtra(String name)
    {
        bundleStates.put(name, BUNDLE_STATE_EXTRA);
        extraMessageCounts.put(name, 0);
    }
    
    /**
     * Reset the comparison
     */
    public void reset()
    {
        bundleStates = new HashMap<String, Integer>();
        missingMessageCounts = new HashMap<String, Integer>();
        extraMessageCounts = new HashMap<String, Integer>();
        bundleResults = new HashMap<String, BundleComparisonResult>();
        baseBundles = new HashMap<String, MessageBundle>();
        comparisonBundles = new HashMap<String, MessageBundle>();
    }

    public BundleComparisonResult getComparisonResult(String path)
    {
        return bundleResults.get(path);
    }
    
    public Integer getBundleState(String path)
    {
        return bundleStates.get(path);
    }
    
    public Set<String> getBundles()
    {
        return new TreeSet<String>(bundleStates.keySet());
    }
    
    public Set<String> getMissingBundles()
    {
        Set<String> bundles = new HashSet<String>();
        for (String bundle : getBundles())
        {
            if (getBundleState(bundle) == BUNDLE_STATE_MISSING)
            {
                bundles.add(bundle);
            }
        }
        return bundles;
    }
    
    public Set<String> getExtraBundles()
    {
        Set<String> bundles = new TreeSet<String>();
        for (String bundle : getBundles())
        {
            if (getBundleState(bundle) == BUNDLE_STATE_EXTRA)
            {
                bundles.add(bundle);
            }
        }
        return bundles;
    }
    
    
    public int getNumBundles()
    {
        int total = 0;
        for (String bundle : getBundles())
        {
            if (getBundleState(bundle) == BUNDLE_STATE_OK)
            {
                total ++;
            }
        }
        return total;
    }
    
    public int getNumMissingBundles()
    {
        return getMissingBundles().size();
    }
    
    public int getNumExtraBundles()
    {
        return getExtraBundles().size();
    }
    public int getNumMessages(String bundle)
    {
        int total = 0;
        BundleComparisonResult result;
        
        if (getBundleState(bundle) == BUNDLE_STATE_OK
                && (result = bundleResults.get(bundle)) != null)
        {
            total += result.getThisMessageCount();
        }
        return total;
    }
    
    /**
     * Return the total number of messages across all bundles in the base 
     * package, not counting any extra bundles.
     * 
     * @return
     */
    public int getNumMessages()
    {
        int total = 0;
        BundleComparisonResult result;
        
        for (String bundle : getBundles())
        {
            if (getBundleState(bundle) == BUNDLE_STATE_OK
                    && (result = bundleResults.get(bundle)) != null)
            {
                total += result.getThisMessageCount();
            }
        }
        return total;
    }
    
    public int getNumMissingMessages(String bundle)
    {
        int total = 0;
        if (getBundleState(bundle) == BUNDLE_STATE_MISSING || getBundleState(bundle) == BUNDLE_STATE_OK)
        {
            total = missingMessageCounts.get(bundle).intValue();
        }
        return total;
    }
    
    public int getNumMissingMessages()
    {
        int total = 0;
        for (String bundle : getBundles())
        {
            total += getNumMissingMessages(bundle);
        }
        return total;
    }
    
    public int getNumExtraMessages(String bundle)
    {
        int total = 0;
        if (getBundleState(bundle) == BUNDLE_STATE_EXTRA || getBundleState(bundle) == BUNDLE_STATE_OK)
        {
            total = extraMessageCounts.get(bundle).intValue();
        }
        return total;
    }
    
    public int getNumExtraMessages()
    {
        int total = 0;
        for (String bundle : getBundles())
        {
            total += getNumExtraMessages(bundle);
        }
        return total;
    }
    
    public float getBundleCoverage()
    {
        int totalFiles = getNumBundles();
        int missingBundles = getNumMissingBundles();
        return ((float) totalFiles / (float) (totalFiles + missingBundles));
    }
    
    public float getMessageCoverage()
    {
        int totalMessages = getNumMessages();
        int missingMessages = getNumMissingMessages();
        return ((float) totalMessages / (float) (totalMessages + missingMessages));
    }
    
    public Set<String> getBundlesWithMissingMessages()
    {

        Set<String> bundles = new TreeSet<String>();
        for (String bundle : getBundles())
        {
            if (getBundleState(bundle) == BUNDLE_STATE_MISSING || 
                    (getBundleState(bundle) == BUNDLE_STATE_OK &&
                            getComparisonResult(bundle) != null && getComparisonResult(bundle).getNumMissingMessages() > 0))
            {
                bundles.add(bundle);
            }
        }
        return bundles;
    }
    
    public Set<String> getBundlesWithExtraMessages()
    {

        Set<String> bundles = new TreeSet<String>();
        for (String bundle : getBundles())
        {
            if (getBundleState(bundle) == BUNDLE_STATE_EXTRA || 
                    (getBundleState(bundle) == BUNDLE_STATE_OK &&
                            getComparisonResult(bundle) != null && getComparisonResult(bundle).getNumExtraMessages() > 0))
            {
                bundles.add(bundle);
            }
        }
        return bundles;
}
    
    public MessageBundle getBaseBundle(String name)
    {
        return baseBundles.get(name);
    }

    public MessageBundle getComparisonBundle(String name)
    {
        return comparisonBundles.get(name);
    }
    
    /**
     * Returns a boolean value indicating whether or not the two bundle groups were identical, according to the
     * comparison
     * 
     * @return	True if the groups were identical, false otherwise
     */
    public boolean isIdentical()
    {
    	return getNumExtraBundles() == 0 && getNumMissingBundles() == 0 && 
    		getNumExtraMessages() == 0 && getNumMissingMessages() == 0;
    }
    
}
